package core

import (
	"fmt"
	"google.golang.org/protobuf/proto"
	"math/rand"
	"mmo_game_zinx/pb"
	"sync"
	"zinx/ziface"
)

// 玩家对象
type Player struct {
	Pid  int32              // 玩家id
	Conn ziface.IConnection // 当前玩家的链接(用于和客户端的链接)
	X    float32            // 平面坐标
	Y    float32            // 高度坐标
	Z    float32            // 平面y坐标,（注意不是Y）
	V    float32            // 旋转的0-369的视角度
}

var PidGen int32 = 0
var IdLock sync.Mutex

// 创建一个玩家的方法
func NewPlayer(conn ziface.IConnection) *Player {
	IdLock.Lock()
	PidGen++
	IdLock.Unlock()
	return &Player{
		Pid:  PidGen,
		Conn: conn,
		X:    float32(160 + rand.Intn(10)), // 随机160坐标点，基于x轴若干偏移
		Y:    0,
		Z:    float32(140 + rand.Intn(20)), // 随机在140坐标点，基于Y轴若干偏移
		V:    0,                            // 角度为0
	}
}

// 发送给客户端消息的方法（主要是把pb的protobuf的数据序列化后，再调用zinx的SendMsg方法）
func (p *Player) SendMsg(msgId uint32, message proto.Message) {
	// 将proto Message 结构体序列化转化成二进制
	data, err := proto.Marshal(message)
	if err != nil {
		fmt.Println("marshal msg error: ", err)
		return
	}

	// 将二进制文件通过zinx框架的sendMsg将数据发送给客户端
	if p.Conn == nil {
		fmt.Println("connection in player is nil")
		return
	}

	if err := p.Conn.SendMsg(msgId, data); err != nil {
		fmt.Println("player sendMsg error! msgId===>", msgId, "error===>", err)
		return
	}

	return
}

// 告知客户端玩家Pid,同步已经生成的玩家ID给客户端
func (p *Player) SyncPid() {
	// 组建msgId:1的proto数据
	data := &pb.SyncPid{
		Pid: p.Pid,
	}

	// 将消息发送给客户端
	p.SendMsg(1, data)
}

// 广播玩家自己的出生地点
func (p *Player) BroadCastStartPosition() {
	// 组建MsgId:200的proto数据
	data := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2, // 代表玩家位置类型广播
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}

	// 发送消息给客户端
	p.SendMsg(200, data)
}

// 玩家广播聊天内容
func (p *Player) Talk(content string) {
	// 1. 组建msgId:200 proto数据
	msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  1,
		Data: &pb.BroadCast_Content{
			Content: content,
		},
	}
	// 2. 得到当前世界所有的在线玩家
	players := WorldMgrObj.GetAllPlayers()

	// 3 向所有的玩家(包括自己)发送MsgId:200消息
	for _, player := range players {
		// player分别给对应的客户端发送消息
		player.SendMsg(200, msg)
	}
}

// 同步玩家上线的位置消息
func (p *Player) SyncSurrounding() {
	// 获取当前玩家周围的玩家
	players := p.GetSurroundingPlayers()

	// 将当前的玩家的位置信息通过msgId:200, 发送给周围的玩家(让其他玩家看到自己)
	// 1. 组建一个msgId 200 的消息
	msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  2, //Tp2 代表广播坐标
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}
	// 2. 全部周围的玩家都向格子的客户端发送200消息
	for _, player := range players {
		player.SendMsg(200, msg)
	}

	// 将周围的全部玩家位置发送给当前玩家msgId:202, 让当前客户可以看到其他玩家
	// 1. 组建msgId:202 proto数据

	playersMsg := make([]*pb.Player, 0, len(players))

	for _, player := range players {
		playerMsg := &pb.Player{
			Pid: player.Pid,
			P: &pb.Position{
				X: player.X,
				Y: player.Y,
				Z: player.Z,
				V: player.V,
			},
		}
		playersMsg = append(playersMsg, playerMsg)
	}
	syncPlayersMsg := &pb.SyncPlayers{
		Ps: playersMsg,
	}
	// 2. 将组建好的数据发送给当前玩家
	p.SendMsg(202, syncPlayersMsg)
}

// 更新当前玩家的位置以及广播给其他玩家
func (p *Player) UpdatePosition(x, y, z, v float32) {

	// 更新当前玩家的位置
	p.X = x
	p.Y = y
	p.Z = z
	p.V = v

	// 发送给其他玩家
	players := p.GetSurroundingPlayers()

	// 将当前的玩家的位置信息通过msgId:200, 发送给周围的玩家(让其他玩家看到自己)
	// 1. 组建一个msgId 200 的消息
	msg := &pb.BroadCast{
		Pid: p.Pid,
		Tp:  4, //Tp2 代表位置更新
		Data: &pb.BroadCast_P{
			P: &pb.Position{
				X: p.X,
				Y: p.Y,
				Z: p.Z,
				V: p.V,
			},
		},
	}
	// 发送给其他玩家
	for _, player := range players {
		player.SendMsg(200, msg)
	}
}

// 获取当前玩家周围已上线的玩家
func (p *Player) GetSurroundingPlayers() []*Player {
	pIds := WorldMgrObj.AoiMgr.GetPlayerIdsByPosition(p.X, p.Z)
	players := make([]*Player, 0, len(pIds))
	for _, pid := range pIds {
		player := WorldMgrObj.GetPlayerByPid(int32(pid))
		players = append(players, player)
	}

	return players
}

func (p *Player) Offline() {
	// 得到当前玩家的周围所有玩家
	players := p.GetSurroundingPlayers()

	// 给周围玩家广播msgId: 201 消息
	msg := &pb.SyncPid{
		Pid: p.Pid,
	}

	for _, player := range players {
		if p.Pid == player.Pid {
			continue
		}
		player.SendMsg(201, msg)
	}

	// 将当前玩家从世界管理器删除
	WorldMgrObj.RemovePlayerByPid(p.Pid)
	// 将当前玩家从AOI管理器删除
	WorldMgrObj.AoiMgr.RemovePlayerByPosition(int(p.Pid), p.X, p.Z)
}
